package com.diaohw.platform.utility.uml.core;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ClassUtil;
import com.diaohw.platform.common.obj.page.PageResultVo;
import com.diaohw.platform.common.util.ClassUtils;
import com.diaohw.platform.utility.uml.core.enums.AccessModifierEnum;
import com.diaohw.platform.utility.uml.core.enums.ClassTypeEnum;
import com.diaohw.platform.utility.uml.core.obj.ClassBO;
import com.diaohw.platform.utility.uml.core.obj.FieldBO;
import com.diaohw.platform.utility.uml.core.obj.MethodBO;
import com.diaohw.platform.utility.uml.core.obj.RelationshipBo;
import net.sourceforge.plantuml.FileFormat;
import net.sourceforge.plantuml.FileFormatOption;
import net.sourceforge.plantuml.SourceStringReader;
import net.sourceforge.plantuml.core.DiagramDescription;
import org.springframework.cloud.openfeign.FeignClient;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.stream.Collectors;

import static com.diaohw.platform.utility.uml.core.enums.RelationshipEnum.Association;
import static com.diaohw.platform.utility.uml.core.enums.RelationshipEnum.Realization;


public class ControllerUML {


    private final Set<RelationshipBo> relationships = new HashSet<>();

    private final LinkedBlockingQueue<Class<?>> queues = new LinkedBlockingQueue<>();

    private final Set<Class<?>> set = new HashSet<>();

    public ControllerUML() {
    }

    public ControllerUML(Set<RelationshipBo> relationships) {
        this.relationships.addAll(relationships);
    }

    /**
     * 创建UML内容
     *
     * @param clazz 要生成UML内容的类
     * @return 表示UML内容的字符串
     */
    public String createContent(Class<?> clazz) {
        // 使用默认的UMLOptions创建UML内容
        return createContent(clazz, new UMLOptions());
    }


    /**
     * 创建UML内容
     *
     * @param clazz   要生成UML内容的类
     * @param options 配置项
     * @return 表示UML内容的字符串
     */
    public String createContent(Class<?> clazz, UMLOptions options) {
        // 检查类是否在指定的基础包中，并将其添加到队列中
        check(clazz, options.getBasePackage());

        // 执行转换为UML格式的操作
        Set<ClassBO> execute = execute(options);

        // 将转换后的类信息集合转换为UML内容的字符串表示形式
        return toUMLContent(execute, options);
    }


    /**
     * 转换为UML格式
     */
    public File create(Class<?> clazz, String outfile, FileFormat fileFormat) throws IOException {

        String sb = "@startuml\n" +
                createContent(clazz) +
                "@enduml";
        System.out.println(sb);

        File file = new File(outfile);

        SourceStringReader sourceStringReader = new SourceStringReader(sb, StandardCharsets.UTF_8);

        DiagramDescription image = sourceStringReader.outputImage(FileUtil.getOutputStream(file), new FileFormatOption(fileFormat));
        System.out.println(image.getDescription());
        // 输出图片文件路径
        System.out.println("Image generated at: " + file.getAbsolutePath());
        return file;
    }

    /**
     * 执行转换为UML格式的操作
     *
     * @param options 配置项
     * @return 包含类信息的ClassBO对象集合
     */
    public Set<ClassBO> execute(UMLOptions options) {
        // 创建一个集合来存储转换后的类信息
        Set<ClassBO> classInfos = new HashSet<>();
        // 当队列不为空时，继续处理队列中的类
        while (!queues.isEmpty()) {
            // 从队列中取出一个类
            Class<?> poll = queues.poll();

            // 获取当前类的类型（接口、抽象类、枚举或普通类）
            ClassTypeEnum format = ClassTypeEnum.format(poll);
            // 创建一个ClassBO对象来表示当前类
            ClassBO classInfo = ClassBO.of(poll.getSimpleName(), format);


            // 如果当前类是枚举类型，处理枚举常量
            if (format == ClassTypeEnum.ENUM) {
                // 遍历枚举常量
                for (Object constant : poll.getEnumConstants()) {
                    // 将枚举常量添加到ClassBO对象的字段集合中
                    classInfo.getFields().add(FieldBO.of(constant.toString()));
                }
            } else {
                // 获取当前类的字段信息
                Set<FieldBO> fields = fields(poll, options);
                // 设置ClassBO对象的字段集合
                classInfo.setFields(fields);
            }
            // 获取当前类的方法信息
            Set<MethodBO> methods = methods(poll, options);
            // 设置ClassBO对象的方法集合
            classInfo.setMethods(methods);
            // 将ClassBO对象添加到集合中
            classInfos.add(classInfo);
        }
        // 返回转换后的类信息集合
        return classInfos;
    }


    /**
     * 读取字段
     *
     * @param clazz   Class 类信息
     * @param options 配置项
     * @return 字段的列表
     */
    public Set<FieldBO> fields(Class<?> clazz, UMLOptions options) {
        // 获取类的所有声明字段
        Field[] fields = ClassUtil.getDeclaredFields(clazz);
        // 创建一个集合来存储字段信息
        Set<FieldBO> list = new HashSet<>();

        // 遍历每个字段
        for (Field field : fields) {
            // 获取字段类型的访问修饰符
            AccessModifierEnum formatted = AccessModifierEnum.format(field.getType());
            // 如果字段名包含"&"，则跳过该字段
            if (options.isIgnoredF(field.getName())) {
                continue;
            }

            // 获取字段的类型名称
            String genericType = UMLUtils.processTpeName(field.getGenericType().getTypeName());

            // 创建一个FieldBO对象来表示当前字段
            FieldBO attr = FieldBO.of(field.getName(), genericType, formatted);
            // 设置字段是否为静态字段
            attr.setStaticAttr(Modifier.isStatic(field.getModifiers()));
            // 将字段添加到集合中
            list.add(attr);

            if (!options.isRecursionFeign() && clazz.isAnnotationPresent(FeignClient.class)) {
                continue;
            }

            // 如果字段类型在指定的基础包中，则添加一个关联关系到关系集合中
            if (check(field.getType(), options.getBasePackage())) {
                relationships.add(RelationshipBo.of(clazz.getSimpleName(), field.getType().getSimpleName(), Association));
            }

            if (field.getType().equals(Object.class)) {
                continue;
            }

            // 遍历字段类型的所有子接口和子实现类
            for (Class<?> inter : ClassUtils.children(field.getType())) {
                // 如果子接口或子实现类在指定的基础包中，则添加一个实现关系到关系集合中

                if (check(inter, options.getBasePackage())) {

                    RelationshipBo relationship = RelationshipBo.of(field.getType().getSimpleName(), inter.getSimpleName(), Realization);
                    if (relationship == null) {
                        continue;
                    }
                    ClassTypeEnum format = ClassTypeEnum.format(field.getType());
                    if (format == ClassTypeEnum.ABSTRACT || format == ClassTypeEnum.INTERFACE) {
                        options.setParentRelationship(inter.getSimpleName(), String.format(" <%s %s> ", format.name.toLowerCase(), field.getType().getSimpleName()));

                    }
                    relationships.add(relationship);
                }
            }
        }
        // 返回字段信息的集合
        return list;
    }


    /**
     * 读取方法
     *
     * @param clazz   Class 类信息
     * @param options 配置项
     * @return 方法的列表
     */
    public Set<MethodBO> methods(Class<?> clazz, UMLOptions options) {
        // 获取类的所有声明方法
        Method[] methods = clazz.getDeclaredMethods();
        // 创建一个集合来存储方法信息
        Set<MethodBO> list = new HashSet<>();
        // 遍历每个方法
        for (Method method : methods) {
            // 如果方法名在忽略列表中，则跳过该方法
            if (options.isIgnoredM(method.getName())) {
                continue;
            }

            // 获取方法返回类型的访问修饰符
            AccessModifierEnum formatted = AccessModifierEnum.format(method.getReturnType());

            // 创建一个MethodBO对象来表示当前方法
            MethodBO attr = MethodBO.of(method.getName(), UMLUtils.processTpeName(method.getGenericReturnType().getTypeName()), formatted);
            // 设置方法是否为静态方法
            attr.setStaticAttr(Modifier.isStatic(method.getModifiers()));
            // 将方法添加到集合中
            list.add(attr);

            // 获取方法的参数列表
            List<String> parameters = parameters(clazz, method, options);
            // 设置方法的参数列表
            attr.setParams(parameters);

            // 处理方法的泛型参数
            args(clazz, method, options);
        }
        // 返回方法信息的集合
        return list;
    }


    /**
     * 读取方法的参数
     *
     * @param clazz   Class 对象
     * @param method  方法
     * @param options 配置项
     * @return 方法参数的列表
     */
    public List<String> parameters(Class<?> clazz, Method method, UMLOptions options) {
        // 创建一个列表来存储方法的参数
        List<String> list = new ArrayList<>();
        // 遍历方法的参数
        for (Parameter parameter : method.getParameters()) {
            // 处理参数的类型名称
            String s = UMLUtils.processTpeName(parameter.getParameterizedType().getTypeName());
            // 将处理后的类型名称添加到列表中
            list.add(s);
            if (!options.isRecursionFeign() && clazz.isAnnotationPresent(FeignClient.class)) {
                continue;
            }
            // 如果参数类型在指定的基础包中，则添加一个关联关系到关系集合中
            if (check(parameter.getType(), options.getBasePackage())) {
                relationships.add(RelationshipBo.of(clazz.getSimpleName(), parameter.getType().getSimpleName(), Association));
            }
        }
        // 返回方法参数的列表
        return list;
    }


    /**
     * 读取泛型上的参数
     *
     * @param clazz   Class 对象
     * @param method  方法
     * @param options 配置项
     */
    public void args(Class<?> clazz, Method method, UMLOptions options) {
        // 获取方法的返回类型名称
        String typeName = method.getGenericReturnType().getTypeName();
        // 创建一个列表来存储解析出的泛型参数类型
        List<Class<?>> args = new ArrayList<>();

        // 循环解析泛型参数类型
        while (true) {
            // 查找泛型参数列表的开始位置
            int i = typeName.indexOf("<");
            // 查找泛型参数列表的结束位置
            int e = typeName.lastIndexOf(">");
            // 如果没有找到泛型参数列表的开始位置，说明已经解析完毕
            if (i == -1) {
                // 如果类型名称以指定的基础包名开头，并且不包含内部类的符号$，则加载该类并添加到列表中
                if (typeName.startsWith(options.getBasePackage()) && !typeName.contains("$")) {
                    Class<?> aClass = ClassUtil.loadClass(typeName);
                    args.add(aClass);
                }
                break;
            }
            // 提取泛型参数列表之前的类型名称
            String substring = typeName.substring(0, i);
            // 如果该类型名称以指定的基础包名开头，则加载该类并添加到列表中
            if (substring.startsWith(options.getBasePackage())) {
                Class<?> aClass = ClassUtil.loadClass(substring);
                args.add(aClass);
            }
            // 更新类型名称，继续解析剩余的泛型参数
            typeName = typeName.substring(i + 1, e);
        }

        // 移除列表中与当前类相同的类型
        args.remove(clazz);
        // 遍历解析出的泛型参数类型
        for (Class<?> arg : args) {
            // 如果参数类型是PageResultVo，则跳过
            if (arg.equals(PageResultVo.class)) {
                continue;
            }
            // 如果参数类型的名称不包含指定的基础包名，则跳过
            if (!arg.getTypeName().contains(options.getBasePackage())) {
                continue;
            }
            // 如果参数类型是List或Set，或者当前类是基本类型，则跳过
            if (arg.getSimpleName().equals("List") || arg.getSimpleName().equals("Set") || clazz.isPrimitive()) {
                continue;
            }
            if (!options.isRecursionFeign() && clazz.isAnnotationPresent(FeignClient.class)) {
                continue;
            }
            // 如果参数类型在指定的基础包中，则添加一个关联关系到关系集合中
            if (check(arg, options.getBasePackage())) {
                relationships.add(RelationshipBo.of(clazz.getSimpleName(), arg.getSimpleName(), Association));
            }
        }
    }

    /**
     * 检查类是否在指定的基础包中，并将其添加到队列中。
     *
     * @param clazz       要检查的类。
     * @param basePackage 基础包的名称。
     * @return 如果类在基础包中，则返回true；否则返回false。
     */
    public boolean check(Class<?> clazz, String basePackage) {
        // 检查类的包名是否以指定的基础包名开头
        boolean b = clazz.getPackage() != null && clazz.getPackage().getName().startsWith(basePackage);
        // 如果类在基础包中，并且集合中不包含该类，则将其添加到集合和队列中
        if (b && set.add(clazz)) {
            System.out.println("queue add " + clazz);
            queues.add(clazz);
        }
        // 返回检查结果
        return b;
    }

    /**
     * 将一组ClassBO对象转换为UML内容的字符串表示形式。
     *
     * @param classes 包含类信息的ClassBO对象集合。
     * @return 表示UML内容的字符串。
     */
    private String toUMLContent(Set<ClassBO> classes, UMLOptions options) {
        // 创建一个StringBuilder对象，用于构建UML内容字符串
        StringBuilder sb = new StringBuilder();

        Set<String> set = classes.stream().filter(c -> !c.isFlag()).map(ClassBO::getClassName).collect(Collectors.toSet());

        // 遍历每个ClassBO对象
        classes.stream().filter(ClassBO::isFlag).forEach(clazz -> {
            // 添加类的声明，包括类的类型和名称
            sb.append(String.format("%s %s %s {\n", clazz.getType().name, clazz.getClassName(), options.getParentRelationship(clazz.getClassName())));
            // 遍历类的方法
            clazz.getMethods().forEach(method -> {
                // 添加方法的声明，包括修饰符、名称、参数和返回类型
                sb.append(String.format("\t %s %s(%s): %s\n", method.getModifier().modifier, method.getName(), String.join(", ", method.getParams()), method.getClassType()));
            });

            // 如果类是枚举类型，处理枚举常量
            if (clazz.getType() == ClassTypeEnum.ENUM) {
                // 将枚举常量连接成一个字符串，每个常量之间用分号和换行符分隔
                String collect = clazz.getFields().stream().map(FieldBO::getName).collect(Collectors.joining(";\n "));
                // 添加枚举常量字符串
                sb.append(collect).append("\n");
            } else {
                // 遍历类的字段
                clazz.getFields().forEach(f -> {
                    // 添加字段的声明，包括修饰符、名称和类型
                    sb.append(String.format("\t %s %s: %s\n", f.getModifier().modifier, f.getName(), f.getClassType()));
                });
            }

            // 添加类的结束括号
            sb.append("}\n");
        });
        // 移除关系集合中的空值
        relationships.remove(null);
        // 遍历关系集合
        relationships.forEach(relationship -> {

            if (set.contains(relationship.getSource()) || set.contains(relationship.getTarget())) {
                return;
            }

            // 添加关系的声明，包括源类、关系类型和目标类
            sb.append(String.format("%s %s %s\n", relationship.getSource(), relationship.getType().value, relationship.getTarget()));
        });
        // 返回构建好的UML内容字符串
        return sb.toString();
    }

    public Set<Class<?>> classes() {
        return new HashSet<>(set);
    }
}
